package com.stox.client.ui.javafx.workbench.view;

import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.effect.PerspectiveTransform;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.util.Duration;

public class FlippablePane extends StackPane {

	private static final Double PIE = Math.PI;
    private static final Double HALF_PIE = Math.PI / 2;
    private static final double ANIMATION_DURATION = 3000;
    private static final double ANIMATION_RATE = 10;
    private SimpleObjectProperty<Region> frontNodeProperty = new SimpleObjectProperty<Region>();
    private SimpleObjectProperty<Region> backNodeProperty = new SimpleObjectProperty<Region>();
    private Timeline animation;
    private SimpleDoubleProperty angle = new SimpleDoubleProperty(HALF_PIE);
    private PerspectiveTransform transform = new PerspectiveTransform();
    private SimpleBooleanProperty flippedProperty = new SimpleBooleanProperty();
    private ChangeListener<Number> transformChangeListener = new ChangeListener<Number>() {
        @Override public void changed(final ObservableValue<? extends Number> obsValue, final Number oldValue, final Number newValue){
            recalculateTransformation(angle.doubleValue());
        }
    };
    private ChangeListener<Region> nodeChangeListener = new ChangeListener<Region>(){
    	@Override
    	public void changed(ObservableValue<? extends Region> observable,
    			Region oldValue, Region newValue) {
    		getChildren().remove(oldValue);
    		if(null != newValue){
    			newValue.setEffect(transform);
    			if(observable.equals(frontNodeProperty)){
    				newValue.visibleProperty().bind(flippedProperty.not());
    			}else if(observable.equals(backNodeProperty)){
    				newValue.visibleProperty().bind(flippedProperty);
    			}
    			getChildren().add(newValue);
    		}
    	}
    };

    private FlippablePane() {
    	this(null);
	}
    
    private FlippablePane(Region frontNode) {
    	this(frontNode, null);
	}
    
    private FlippablePane(Region frontNode, Region backNode) {
    	setMinSize(100, 100);
    	
    	heightProperty().addListener(transformChangeListener);
    	widthProperty().addListener(transformChangeListener);
    	
    	angle.addListener(transformChangeListener);
		frontNodeProperty.addListener(nodeChangeListener);
		backNodeProperty.addListener(nodeChangeListener);
		setFrontNode(frontNode);
		setBackNode(backNode);
		flippedProperty.set(false);
	}

    private Timeline createAnimation(){
        return new Timeline(
                new KeyFrame(Duration.millis(0),    new KeyValue(angle, HALF_PIE)),
                new KeyFrame(Duration.millis(ANIMATION_DURATION / 2),  new KeyValue(angle, 0, Interpolator.EASE_IN)),
                new KeyFrame(Duration.millis(ANIMATION_DURATION / 2),  new EventHandler<ActionEvent>() {
                    @Override public void handle(final ActionEvent arg0){
                        flippedProperty.set( flippedProperty.not().get() );
                    }
                }),
                new KeyFrame(Duration.millis(ANIMATION_DURATION / 2),  new KeyValue(angle, PIE)),
                new KeyFrame(Duration.millis(ANIMATION_DURATION), new KeyValue(angle, HALF_PIE, Interpolator.EASE_OUT))
                );
    }

    public void flip(){
        if (animation == null)
            animation = createAnimation();
        animation.setRate( flippedProperty.get() ? ANIMATION_RATE : -ANIMATION_RATE );
        animation.play();
    }

    private void recalculateTransformation(final double angle){
        final double insetsTop = getInsets().getTop() * 2;
        final double insetsLeft = getInsets().getLeft() * 2;
        final double radius = widthProperty().subtract(insetsLeft).divide(2).doubleValue();
        final double height = heightProperty().subtract(insetsTop).doubleValue();
        final double back = height / 10;
        transform.setUlx(radius - Math.sin(angle) * radius);
        transform.setUly(0 - Math.cos(angle) * back);
        transform.setUrx(radius + Math.sin(angle) * radius);
        transform.setUry(0 + Math.cos(angle) * back);
        transform.setLrx(radius + Math.sin(angle) * radius);
        transform.setLry(height - Math.cos(angle) * back);
        transform.setLlx(radius - Math.sin(angle) * radius);
        transform.setLly(height + Math.cos(angle) * back);
    }

	public void setFrontNode(Region region){
		frontNodeProperty.set(region);
	}
	
	public void setBackNode(Region region){
		backNodeProperty.set(region);
	}
	
	public Region getFrontNode(){
		return frontNodeProperty.get();
	}
	
	public Region getBackNode(){
		return backNodeProperty.get();
	}
}
